package com.dreamdance.queerball;

import java.util.ArrayList;
import java.util.Iterator;

import android.graphics.Point;
import android.util.Log;
import net.ds0101.framework.android.neox.map.NXField2D;

public class ChessboardData {
    public interface DataChangedListener {
    	public void onNewBallPerch(int x, int y, int colorIndex);
    	public void onBallClearUp(int x, int y);
    	public void onMove(int destX, int destY, int srcX, int srcY, int colorIndex);
    	public void onNewBallsGenerate(int[] colors);
    	public void onCram();
    }
    
    DataChangedListener mDataListener;
	public static final int GRID_NUM = 9;
    public static final int GRID_TOTAL = GRID_NUM * GRID_NUM;
    public static final int NEW_BALLS_NUM = 3;
    public static final int VACANT_GRID = -1;
    public static final int ELIMINATE_THRESHOLD = 5;
    public static final int [] LIMITS = {0, 1, 2, 3, 4, 5, 6, 7};
	public static final int OMNIPOTENT_INDEX = 7; // color index for omnipotent ball.
	public static final int OMNI_OCCUR_PROBABILITY_REDUCE = 2;
	public static final int COLOR_TOTAL = LIMITS.length;
	
	public int [][] mGridMatrix = new int [GRID_NUM][GRID_NUM];
    private int [] mBallsInNextTurn = new int [NEW_BALLS_NUM];

    private Point mGridCoordinate = new Point(0, 0);
    private ArrayList<Short> mEliminatablePosition = new ArrayList<Short>(5);
    private NXField2D mMap = new NXField2D(mGridMatrix, LIMITS);;

    class SameBallCountter {
        private int [] mSameBallCount;

        SameBallCountter(int colorTotal) {
            mSameBallCount = new int[colorTotal];
        }

        SameBallCountter() {
            mSameBallCount = new int[COLOR_TOTAL];
        }

        void increaseCount(int colorIndex) {
            for (int i = 0; i < mSameBallCount.length; ++i) {
                if (i == colorIndex || OMNIPOTENT_INDEX == colorIndex) {
                    ++mSameBallCount[i];
                } else {
                    mSameBallCount[i] = 0;
                }
            }
        }

        int colorTotalArriveThreshold() {
            for (int i = 0; i < mSameBallCount.length; ++i) {
                if (mSameBallCount[i] >= ELIMINATE_THRESHOLD) {
                    return i; // return the arrived color index.
                }
            }
            return VACANT_GRID;
        }

        boolean anyColorCountUp() {
            return colorTotalArriveThreshold() != -1;
        }

        void reset() {
            for (int i = 0; i < mSameBallCount.length; ++i) {
                mSameBallCount[i] = 0;
            }
        }
    }

    SameBallCountter mBallCount = new SameBallCountter();

    public NXField2D map() {
    	return mMap;
    }
    
    public int vacantGridNum() {
        int count = 0;
        for (int[] row : mGridMatrix) {
            for (int elem : row) {
                if (VACANT_GRID == elem) {
                    ++count;
                }
            }
        }
        return count;
    }

    public int fullGridNum() {
        return GRID_NUM * GRID_NUM - vacantGridNum();
    }

    public boolean isAllGridTopful() {
        for (int[] row : mGridMatrix) {
            for (int elem : row) {
                if (VACANT_GRID != elem) {
                    return false;
                }
            }
        }
        return true;
    }

    public void newBallsGenerate() {
        for (int i = 0; i < NEW_BALLS_NUM; ++i) {
            mBallsInNextTurn[i] = (int)(Math.random() * COLOR_TOTAL);
            if (OMNIPOTENT_INDEX == mBallsInNextTurn[i]) {
            	if (((int)(Math.random() * OMNI_OCCUR_PROBABILITY_REDUCE) & 1) != 0) {
            		while (OMNIPOTENT_INDEX == mBallsInNextTurn[i]) {
            			mBallsInNextTurn[i] = (int)(Math.random() * COLOR_TOTAL);
            		}
            	}
            }
        }
        if (null != mDataListener) {
        	mDataListener.onNewBallsGenerate(mBallsInNextTurn);
        }
    }
    
    public Point nextVacantGrid() {
        for (int y = 0; y < GRID_NUM; ++y) {
            for (int x = 0; x < GRID_NUM; ++x) {
                if (VACANT_GRID == mGridMatrix[y][x]) {
                    mGridCoordinate.set(x, y);
                    return mGridCoordinate;
                }
            }
        }
        return null;
    }

    public Point nextVacantGridInRandom() {
        int vacant = vacantGridNum();
        if (vacant > 0) {
            while (true) {
                for (int y = 0; y < GRID_NUM; ++y) {
                    for (int x = 0; x < GRID_NUM; ++x) {
                        if (VACANT_GRID == mGridMatrix[y][x]
                                && ((int)(Math.random() * vacant) == 0)) {
                            mGridCoordinate.set(x, y);
                            return mGridCoordinate;
                        }
                    }
                }
            }
        }
        return null;
    }

    private boolean putBallsToRandomPos(int [] balls) {
        Point pos;
        for (int i = 0; i < balls.length; ++i) {
            pos = nextVacantGridInRandom();
            if (null != pos) {
                mGridMatrix[pos.y][pos.x] = balls[i];
                if (null != mDataListener) {
                	mDataListener.onNewBallPerch(pos.x, pos.y, balls[i]);
                }
            } else {
            	if (null != mDataListener) {
            		mDataListener.onCram();
            	}
                return false;
            }
        }
        // there have no vacant grid in next turn
        if (null == nextVacantGridInRandom()) {
        	if (null != mDataListener) {
        		mDataListener.onCram();
        	}
        }
        return true;
    }

    public boolean putNewBallsToGrid() {
        boolean ret = putBallsToRandomPos(mBallsInNextTurn);
        newBallsGenerate();
        return ret;
    }

    public boolean findEliminatable() {
        int colorIndex = -1;
        int x, y;
        // find landscape direction
        for (y = 0; y < GRID_NUM; ++y) {
            mBallCount.reset();
            colorIndex = VACANT_GRID;
            for (x = 0; x < GRID_NUM; ++x) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[y][x] || OMNIPOTENT_INDEX == mGridMatrix[y][x]) {
                        mEliminatablePosition.add(new Short((short)(y << 8 | x)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[y][x]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (VACANT_GRID != colorIndex) {
                        for (int ox = 0; ox < ELIMINATE_THRESHOLD; ++ox) {
                            mEliminatablePosition.add(new Short((short)(y << 8 | x - ox)));
                            if (!(x - ox < GRID_NUM && y < GRID_NUM && x - ox >= 0 && y >= 0))
                                Log.w("QueerBall-1", "mark pos = (" + String.valueOf(x) + ", " + String.valueOf(y) + ")");
                        }
                    }
                }
            }
        }

        // find portrait direction
        for (x = 0; x < GRID_NUM; ++x) {
            mBallCount.reset();
            colorIndex = VACANT_GRID;
            for (y = 0; y < GRID_NUM; ++y) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[y][x] || OMNIPOTENT_INDEX == mGridMatrix[y][x]) {
                        mEliminatablePosition.add(new Short((short)(y << 8 | x)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[y][x]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (-1 != colorIndex) {
                        for (int oy = 0; oy < ELIMINATE_THRESHOLD; ++oy) {
                            mEliminatablePosition.add(new Short((short)(((y - oy) << 8) | x)));
                            if (!(x < GRID_NUM && y - oy < GRID_NUM && x >= 0 && y - oy >= 0))
                                Log.w("QueerBall-2", "mark pos = (" + String.valueOf(x) + ", " + String.valueOf(y) + ")");
                        }
                    }
                }
            }
        }

        // find oblique direction
        for (y = 0; y < ELIMINATE_THRESHOLD; ++y) {
            mBallCount.reset();
            colorIndex = VACANT_GRID;
            for (int yy = y, xx = 0; yy < GRID_NUM; ++xx, ++yy) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[yy][xx] || OMNIPOTENT_INDEX == mGridMatrix[yy][xx]) {
                        mEliminatablePosition.add(new Short((short)(yy << 8 | xx)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[yy][xx]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (-1 != colorIndex) {
                        for (int o = 0; o < ELIMINATE_THRESHOLD; ++o) {
                            mEliminatablePosition.add(new Short((short)(yy - o << 8 | xx - o)));
                            if (!(xx - o < GRID_NUM && yy - o < GRID_NUM && xx - o >= 0 && yy - o>= 0))
                                Log.w("QueerBall-3", "mark pos = (" + String.valueOf(xx) + ", " + String.valueOf(yy) + ")");
                        }
                    }
                }
            }
        }
        for (x = 1; x < ELIMINATE_THRESHOLD; ++x) {
            mBallCount.reset();
            colorIndex = VACANT_GRID;
            for (int yy = 0, xx = x; xx < GRID_NUM; ++xx, ++yy) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[yy][xx] || OMNIPOTENT_INDEX == mGridMatrix[yy][xx]) {
                        mEliminatablePosition.add(new Short((short)(yy << 8 | xx)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[yy][xx]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (-1 != colorIndex) {
                        for (int o = 0; o < ELIMINATE_THRESHOLD; ++o) {
                            mEliminatablePosition.add(new Short((short)(yy - o << 8 | xx - o)));
                            if (!(xx - o < GRID_NUM && yy - o < GRID_NUM && xx - o >= 0 && yy - o >= 0))
                                Log.w("QueerBall-4", "mark pos = (" + String.valueOf(xx) + ", " + String.valueOf(yy) + ")");
                        }
                    }
                }
            }
        }


        for (x = 0; x < ELIMINATE_THRESHOLD; ++x) {
            mBallCount.reset();
            colorIndex = -1;
            for (int yy = 8, xx = x; xx < GRID_NUM; ++xx, --yy) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[yy][xx] || OMNIPOTENT_INDEX == mGridMatrix[yy][xx]) {
                        mEliminatablePosition.add(new Short((short)(yy << 8 | xx)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[yy][xx]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (-1 != colorIndex) {
                        for (int o = 0; o < ELIMINATE_THRESHOLD; ++o) {
                            mEliminatablePosition.add(new Short((short)(yy + o << 8 | xx - o)));
                            if (!(xx - o < GRID_NUM && yy + o < GRID_NUM && xx - o >= 0 && yy + o >= 0))
                                Log.w("QueerBall-5", "mark pos = (" + String.valueOf(xx) + ", " + String.valueOf(yy) + ")");
                        }
                    }
                }
            }
        }
        for (y = 7; y > 3; --y) {
            mBallCount.reset();
            colorIndex = -1;
            for (int yy = y, xx = 0; yy >= 0; ++xx, --yy) {
                if (VACANT_GRID != colorIndex) {
                    if (colorIndex == mGridMatrix[yy][xx] || OMNIPOTENT_INDEX == mGridMatrix[yy][xx]) {
                        mEliminatablePosition.add(new Short((short)(yy << 8 | xx)));
                    } else {
                        break;
                    }
                } else { // Never find much same color ball in this line.
                    mBallCount.increaseCount(mGridMatrix[yy][xx]);
                    colorIndex = mBallCount.colorTotalArriveThreshold();
                    if (-1 != colorIndex) {
                        for (int o = 0; o < ELIMINATE_THRESHOLD; ++o) {
                            mEliminatablePosition.add(new Short((short)(yy + o << 8 | xx - o)));
                            if (!(xx - o < GRID_NUM && yy + o < GRID_NUM && xx - o >= 0 && yy + o >= 0))
                                Log.w("QueerBall-6", "mark pos = (" + String.valueOf(xx) + ", " + String.valueOf(yy) + ")");
                        }
                    }
                }
            }
        }
        return false;
    }

    public int clearMarkedGrid() {
        Iterator<Short> iter = mEliminatablePosition.iterator();
        int x, y;
        int ret = 0;
        if (mEliminatablePosition.size() > 0) {
        	while (iter.hasNext()) {
        		short pos = ((Short)iter.next()).shortValue();
        		x = pos & 0xff;
        		y = (pos >> 8);
        		if (x < GRID_NUM && y < GRID_NUM && x >= 0 && y >= 0) {
        			mGridMatrix[y][x] = VACANT_GRID;
        			++ret;
        			if (null != mDataListener) {
        				mDataListener.onBallClearUp(x, y);
        			}
        		} else {
        			Log.w("[QueerBall]", "mark pos = (" + String.valueOf(x) + ", " + String.valueOf(y) + ")");
        		}
        	}
        }
        mEliminatablePosition.clear();
        return ret;
    }

    public void reset() {
        for (int y = 0; y < GRID_NUM; ++y) {
            for (int x = 0; x < GRID_NUM; ++x) {
                mGridMatrix[y][x] = VACANT_GRID;
            }
        }
    }
    
    public boolean isVacant(int x, int y) {
    	if (x >= 0 && x < GRID_NUM && y >= 0 && y < GRID_NUM) {
    		return (VACANT_GRID == mGridMatrix[y][x]);
    	}
    	Log.w("[QueerBall]", "isVacant param = (" + String.valueOf(x) + ", " + String.valueOf(y) + ")");
    	return false;
    }
    
    public boolean isFilled(int x, int y) {
    	return (!isVacant(x, y));
    }
    
    public int colorIndex(int x, int y) {
    	return mGridMatrix[y][x];
    }
    
    public boolean moveBall(int destX, int destY, int srcX, int srcY) {
    	if (VACANT_GRID != mGridMatrix[destY][destX]) {
    		return false;
    	} else {
    		mGridMatrix[destY][destX] = mGridMatrix[srcY][srcX];
    		mGridMatrix[srcY][srcX] = VACANT_GRID;
    		if (null != mDataListener) {
    			mDataListener.onMove(destX, destY, srcX, srcY, mGridMatrix[destY][destX]);
    		}
    		return true;
    	}
    }
    
    public void registerDataChangedListener(DataChangedListener listener) {
    	mDataListener = listener;
    }
    
    public void unregisterDataChangedListener() {
    	mDataListener = null;
    }
}
